/*
 * 
 *	Copyright (C) 2011 -- Alfred Reibenschuh
 *
 * 	This program is free software; you can redistribute it and/or 
 *	modify it under the terms of the GNU General Public License as 
 *	published by the Free Software Foundation; either version 2 of 
 *	the License, or (at your option) any later version.
 *
 *	This program is distributed in the hope that it will be useful, 
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of 
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
 *	GNU General Public License for more details.
 * 
 */

package redoak.php.snmp;

import java.io.IOException;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Vector;
import java.util.concurrent.TimeoutException;

import org.opennms.protocols.snmp.SnmpBadConversionException;
import org.opennms.protocols.snmp.SnmpHandler;
import org.opennms.protocols.snmp.SnmpIPAddress;
import org.opennms.protocols.snmp.SnmpInt32;
import org.opennms.protocols.snmp.SnmpObjectId;
import org.opennms.protocols.snmp.SnmpOctetString;
import org.opennms.protocols.snmp.SnmpPduPacket;
import org.opennms.protocols.snmp.SnmpPduRequest;
import org.opennms.protocols.snmp.SnmpPeer;
import org.opennms.protocols.snmp.SnmpSMI;
import org.opennms.protocols.snmp.SnmpSession;
import org.opennms.protocols.snmp.SnmpSyntax;
import org.opennms.protocols.snmp.SnmpTimeTicks;
import org.opennms.protocols.snmp.SnmpUInt32;
import org.opennms.protocols.snmp.SnmpVarBind;

import com.caucho.util.Hex;



public class JoeSnmpModuleSession extends SnmpSession implements SnmpHandler, ISnmpModuleSession 
{
	public static final int SNMP_BIT_STR   = SnmpSMI.SMI_STRING;
	public static final int SNMP_OCTET_STR = SnmpSMI.SMI_STRING;
	public static final int SNMP_OPAQUE    = SnmpSMI.SMI_OPAQUE;
	public static final int SNMP_NULL      = SnmpSMI.SMI_NULL;
	public static final int SNMP_OBJECT_ID = SnmpSMI.SMI_OBJECTID;
	public static final int SNMP_IPADDRESS = SnmpSMI.SMI_IPADDRESS;
	public static final int SNMP_COUNTER   = SnmpSMI.SMI_COUNTER32;
	public static final int SNMP_UNSIGNED  = SnmpSMI.SMI_UNSIGNED32;
	public static final int SNMP_TIMETICKS = SnmpSMI.SMI_TIMETICKS;
	public static final int SNMP_UINTEGER  = SnmpSMI.SMI_UNSIGNED32;
	public static final int SNMP_INTEGER   = SnmpSMI.SMI_INTEGER;
	public static final int SNMP_COUNTER64 = SnmpSMI.SMI_COUNTER64;

	public static final int SNMP_VALUE_LIBRARY = 0;
	public static final int SNMP_VALUE_PLAIN   = 1;
	public static final int SNMP_VALUE_OBJECT  = 2;
	
	public JoeSnmpModuleSession(SnmpPeer peer) throws SocketException 
	{
		super(peer);
		super.setDefaultHandler(this);
		lastUse=System.currentTimeMillis();
	}

	public void setRetries(int v) 
	{
		this.getPeer().setRetries(v);
	}

	public void setTimeout(int v) 
	{
		this.getPeer().setTimeout(v);
	}

	public long lastUse;
	Object req_sync = new Object();
	SnmpPduPacket resp_pdu;
	int resp_code;
	
	public void snmpInternalError(SnmpSession arg0, int arg1, SnmpSyntax arg2) 
	{
		resp_code=-2;
		resp_pdu=null;
		lastUse=System.currentTimeMillis();
		synchronized(req_sync) { req_sync.notifyAll(); }
	}

	public void snmpReceivedPdu(SnmpSession arg0, int arg1, SnmpPduPacket arg2) 
	{
		resp_code=arg1;
		resp_pdu=arg2;
		lastUse=System.currentTimeMillis();
		synchronized(req_sync) { req_sync.notifyAll(); }
	}

	public void snmpTimeoutError(SnmpSession arg0, SnmpSyntax arg1) 
	{
		resp_code=-1;
		resp_pdu=null;
		lastUse=System.currentTimeMillis();
		synchronized(req_sync) { req_sync.notifyAll(); }
	}

	private SnmpPduPacket sendPdu(SnmpPduPacket pdu) throws TimeoutException, InterruptedException, IOException 
	{
		lastUse=System.currentTimeMillis();
		synchronized(req_sync) 
		{
			super.send(pdu);
			req_sync.wait();
		}
		if(resp_code==-1)
		{
			throw new TimeoutException();
		}
		if(resp_code==-2)
		{
			throw new IOException();
		}
		//if(((SnmpPduRequest)resp_pdu).getErrorStatus()>0)
		//{
		//	throw new IllegalArgumentException();
		//}
		return resp_pdu;
	}
	
	public static ISnmpModuleSession createV1(String host, int port, String user) throws UnknownHostException, SocketException
	{
		SnmpPeer snmpPeer = new SnmpPeer(InetAddress.getByName(host));
		snmpPeer.setPort(port);
		snmpPeer.getParameters().setReadCommunity(user);
		snmpPeer.getParameters().setVersion(SnmpSMI.SNMPV1);
		return new JoeSnmpModuleSession(snmpPeer);
	}

	public static ISnmpModuleSession createV2(String host, int port, String user) throws UnknownHostException, SocketException
	{
		SnmpPeer snmpPeer = new SnmpPeer(InetAddress.getByName(host));
		snmpPeer.setPort(port);
		snmpPeer.getParameters().setReadCommunity(user);
		snmpPeer.getParameters().setVersion(SnmpSMI.SNMPV2);
		return new JoeSnmpModuleSession(snmpPeer);
	}

	public static ISnmpModuleSession createV3(String host, int port, String user, String context, String auth, String authkey, String priv, String privkey) throws UnknownHostException, SocketException
	{
		return null;
	}

	public String vbString(SnmpVarBind vb)
	{
		SnmpSyntax v = vb.getValue();
		switch(v.typeId())
		{
		case SnmpSMI.SMI_STRING:
			return new String(((SnmpOctetString)v).getString());
		case SnmpSMI.SMI_TIMETICKS:
			return ""+((SnmpTimeTicks)v).getValue();
		default:
			return v.toString();
		}
	}
	
	public Object[] snmp_get(String oid) throws TimeoutException, InterruptedException, IOException
	{
        SnmpVarBind[] vblist = {new SnmpVarBind(oid)};
        SnmpPduRequest pdu = new SnmpPduRequest(SnmpPduPacket.GET, vblist);
        pdu.setRequestId(SnmpPduPacket.nextSequence());
		SnmpPduPacket resp = this.sendPdu(pdu);
		return new Object[] {resp.getVarBindAt(0).getName().toString(), new Integer(resp.getVarBindAt(0).getValue().typeId()), vbString(resp.getVarBindAt(0))};
	}

	public Object[][] snmp_get(String[] oids) throws TimeoutException, InterruptedException, IOException
	{
		Vector<SnmpVarBind> vbv = new Vector();
		for(String oid : oids)
		{
			vbv.add(new SnmpVarBind(oid));
		}
		SnmpVarBind[] vblist = vbv.toArray(new SnmpVarBind[]{});
        SnmpPduRequest pdu = new SnmpPduRequest(SnmpPduPacket.GET, vblist);
        pdu.setRequestId(SnmpPduPacket.nextSequence());
		SnmpPduPacket resp = this.sendPdu(pdu);
		Vector<Object[]> rv = new Vector();
		for(SnmpVarBind vb : resp.toVarBindArray())
		{
			rv.add(new Object[] {vb.getName().toString(), new Integer(vb.getValue().typeId()), vbString(vb)});
		}
		return rv.toArray(new Object[][]{});
	}

	public Object[] snmp_next(String oid) throws TimeoutException, InterruptedException, IOException
	{
        SnmpVarBind[] vblist = {new SnmpVarBind(oid)};
        SnmpPduRequest pdu = new SnmpPduRequest(SnmpPduPacket.GETNEXT, vblist);
        pdu.setRequestId(SnmpPduPacket.nextSequence());
		SnmpPduPacket resp = this.sendPdu(pdu);
		return new Object[] {resp.getVarBindAt(0).getName().toString(), new Integer(resp.getVarBindAt(0).getValue().typeId()), vbString(resp.getVarBindAt(0))};
	}

	public Object[][] snmp_next(String[] oids) throws TimeoutException, InterruptedException, IOException 
	{
		Vector<SnmpVarBind> vbv = new Vector();
		for(String oid : oids)
		{
			vbv.add(new SnmpVarBind(oid));
		}
		SnmpVarBind[] vblist = vbv.toArray(new SnmpVarBind[]{});
        SnmpPduRequest pdu = new SnmpPduRequest(SnmpPduPacket.GETNEXT, vblist);
        pdu.setRequestId(SnmpPduPacket.nextSequence());
		SnmpPduPacket resp = this.sendPdu(pdu);
		Vector<Object[]> rv = new Vector();
		for(SnmpVarBind vb : resp.toVarBindArray())
		{
			rv.add(new Object[] {vb.getName().toString(), new Integer(vb.getValue().typeId()), vbString(vb)});
		}
		return rv.toArray(new Object[][]{});
	}

	private SnmpSyntax toSnmp(String type, String string) throws IOException
	{
		if(type.startsWith("i")) { return (new SnmpInt32(string)); }
		else if(type.startsWith("u")) { return (new SnmpUInt32(string)); }
		else if(type.startsWith("t")) { return (new SnmpTimeTicks(string)); }
		else if(type.startsWith("a")) 
		{ 
			try 
			{
				return (new SnmpIPAddress(string));
			} 
			catch (SnmpBadConversionException e) 
			{
				e.printStackTrace();
			} 
		}
		else if(type.startsWith("o")) { return (new SnmpObjectId(string)); }
		else if(type.startsWith("s")) { return (new SnmpOctetString(string.getBytes("UTF-8"))); }
		else if(type.startsWith("x")) { return (new SnmpOctetString(Hex.toBytes(string))); }
		else if(type.startsWith("d"))
		{
			String[] nums = string.split(" ");
			byte[] buf = new byte[nums.length];
			for(int i=0; i<nums.length; i++)
			{
				buf[i] = (byte)Integer.parseInt(nums[i]);
			}
			return (new SnmpOctetString(buf));
		}
		return (new SnmpOctetString(string.getBytes()));
	}
	
	public Boolean snmp_set(String oid, String type, String string) throws TimeoutException, InterruptedException, IOException 
	{
		SnmpVarBind vb = new SnmpVarBind(oid);
		vb.setValue(this.toSnmp(type, string));
		SnmpVarBind[] vblist = { vb };
		SnmpPduRequest pdu = new SnmpPduRequest(SnmpPduPacket.SET, vblist);
        pdu.setRequestId(SnmpPduPacket.nextSequence());
		SnmpPduPacket resp = this.sendPdu(pdu);
		return new Boolean(((SnmpPduRequest)resp).getErrorStatus()==0);
	}
	
	public Boolean snmp_set(String[][] values, boolean singlePdu) throws TimeoutException, InterruptedException, IOException 
	{
		if(!singlePdu)
		{
			for(String[] value : values)
			{
				if(!snmp_set(value[0], value[1], value[2]))
				{
					return Boolean.FALSE;
				}
			}
			return Boolean.TRUE;
		}
		else
		{
			Vector<SnmpVarBind> vbv = new Vector();
			for(String[] value : values)
			{
				SnmpVarBind vb = new SnmpVarBind(value[0]);
				vb.setValue(this.toSnmp(value[1], value[2]));
				vbv.add(vb);
			}
			SnmpVarBind[] vblist = vbv.toArray(new SnmpVarBind[]{});
			SnmpPduRequest pdu = new SnmpPduRequest(SnmpPduPacket.SET, vblist);
	        pdu.setRequestId(SnmpPduPacket.nextSequence());
			SnmpPduPacket resp = this.sendPdu(pdu);
			return new Boolean(((SnmpPduRequest)resp).getErrorStatus()==0);
		}
	}

	public Object[][] snmp_walk(String oid, boolean b) throws TimeoutException, InterruptedException, IOException 
	{
		Vector<Object[]> vbs = new Vector();
		String p_oid=oid+".";
		String c_oid=oid+".0";
		
		while(c_oid.startsWith(p_oid))
		{
			Object[] val = snmp_next(c_oid);
			
			if(c_oid.equals(val[0].toString()))
				break;
			if(!val[0].toString().startsWith(p_oid))
				break;
			vbs.add(val);
			c_oid = val[0].toString();
		}

		return vbs.toArray(new Object[][]{});
	}

}
